use barter_instrument::{
    asset::{AssetIndex, name::AssetNameExchange},
    exchange::ExchangeId,
    instrument::{InstrumentIndex, name::InstrumentNameExchange},
};
use barter_integration::error::SocketError;
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// Type alias for a [`ClientError`] that is keyed on [`AssetNameExchange`] and
/// [`InstrumentNameExchange`] (yet to be indexed).
pub type UnindexedClientError = ClientError<AssetNameExchange, InstrumentNameExchange>;

/// Type alias for a [`ApiError`] that is keyed on [`AssetNameExchange`] and
/// [`InstrumentNameExchange`] (yet to be indexed).
pub type UnindexedApiError = ApiError<AssetNameExchange, InstrumentNameExchange>;

/// Type alias for a [`OrderError`] that is keyed on [`AssetNameExchange`] and
/// [`InstrumentNameExchange`] (yet to be indexed).
pub type UnindexedOrderError = OrderError<AssetNameExchange, InstrumentNameExchange>;

/// Represents all errors produced by an [`ExecutionClient`](super::client::ExecutionClient).
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Error)]
pub enum ClientError<AssetKey = AssetIndex, InstrumentKey = InstrumentIndex> {
    /// Connectivity based error.
    ///
    /// eg/ Timeout.
    #[error("Connectivity: {0}")]
    Connectivity(#[from] ConnectivityError),

    /// API based error.
    ///
    /// eg/ RateLimit.
    #[error("API: {0}")]
    Api(#[from] ApiError<AssetKey, InstrumentKey>),

    /// Failed to fetch an AccountSnapshot.
    #[error("failed to fetch AccountSnapshot: {0}")]
    AccountSnapshot(String),

    /// Failed to initialise an AccountStream.
    #[error("failed to init AccountStream: {0}")]
    AccountStream(String),
}

/// Represents all connectivity-centric errors.
///
/// Connectivity errors are generally intermittent / non-deterministic (eg/ Timeout).
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Error)]
pub enum ConnectivityError {
    /// Indicates an exchange is offline, likely due to maintenance.
    #[error("Exchange offline: {0}")]
    ExchangeOffline(ExchangeId),

    /// Indicates an ExecutionRequest timed out before a response was received.
    #[error("ExecutionRequest timed out")]
    Timeout,

    /// Represents a [`SocketError`] generated by an execution integration.
    #[error("{0}")]
    Socket(String),
}

impl From<SocketError> for ConnectivityError {
    fn from(value: SocketError) -> Self {
        Self::Socket(value.to_string())
    }
}

/// Represents all API errors generated by an exchange.
///
/// These typically indicate a request is invalid for some reason (eg/ BalanceInsufficient).
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Error)]
pub enum ApiError<AssetKey = AssetIndex, InstrumentKey = InstrumentIndex> {
    /// Provided asset identifier is invalid or not supported.
    ///
    /// For example:
    /// - The [`AssetNameExchange`] was an invalid format.
    #[error("asset {0} invalid: {1}")]
    AssetInvalid(AssetKey, String),

    /// Provided instrument identifier is invalid or not supported.
    ///
    /// For example:
    /// - The exchange does not have a market for an instrument.
    /// - The [`InstrumentNameExchange`] was an invalid format.
    #[error("instrument {0} invalid: {1}")]
    InstrumentInvalid(InstrumentKey, String),

    #[error("rate limit exceeded")]
    RateLimit,
    #[error("asset {0} balance insufficient: {1}")]
    BalanceInsufficient(AssetKey, String),
    #[error("order rejected: {0}")]
    OrderRejected(String),
    #[error("order already cancelled")]
    OrderAlreadyCancelled,
    #[error("order already fully filled")]
    OrderAlreadyFullyFilled,
}

/// Represents all errors that can be generated when cancelling or opening orders.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Error)]
pub enum OrderError<AssetKey = AssetIndex, InstrumentKey = InstrumentIndex> {
    /// Connectivity based error.
    ///
    /// eg/ Timeout.
    #[error("connectivity: {0}")]
    Connectivity(#[from] ConnectivityError),

    /// API based error.
    ///
    /// eg/ RateLimit.
    #[error("order rejected: {0}")]
    Rejected(#[from] ApiError<AssetKey, InstrumentKey>),
}

/// Represents errors related to exchange, asset and instrument identifier key lookups.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Error)]
pub enum KeyError {
    /// Indicates an [`ExchangeId`] was encountered that was not indexed, so does not have a
    /// corresponding `ExchangeIndex`.
    #[error("ExchangeId: {0}")]
    ExchangeId(String),

    /// Indicates an [`AssetNameExchange`] was encountered that was not indexed, so does not have a
    /// corresponding [`AssetIndex`].
    #[error("AssetKey: {0}")]
    AssetKey(String),

    /// Indicates an [`InstrumentNameExchange`] was encountered that was no indexed, so does
    /// not have a corresponding [`InstrumentIndex`].
    #[error("InstrumentKey: {0}")]
    InstrumentKey(String),
}
